package utils

import (
	"fmt"
	"strings"
	"time"
)

/*
*

	https://cron.qqe2.com/
	Seconds Minutes Hours DayofMonth Month DayofWeek Year
	不支持使用?
*/
type CronExp struct {
	seconds    CronExpItem
	minutes    CronExpItem
	hours      CronExpItem
	dayofmonth string
	month      string
	dayofweek  string
	year       string
	text       string
}

func NewCronExp() *CronExp {
	rval := &CronExp{}
	return rval
}

func getWeekDayVal(s string, v *int) bool {
	switch strings.ToLower(s) {
	case "sun":
		*v = 1
		return true
	case "mon":
		*v = 2
		return true
	case "tue":
		*v = 3
		return true
	case "wed":
		*v = 4
		return true
	case "thu":
		*v = 5
		return true
	case "fri":
		*v = 6
		return true
	case "sat":
		*v = 7
		return true
	}
	return false
}

func getMonthVal(s string, v *int) bool {
	switch strings.ToLower(s) {
	case "jan":
		*v = 1
		return true
	case "feb":
		*v = 2
		return true
	case "mar", "march":
		*v = 3
		return true
	case "apr", "april":
		*v = 4
		return true
	case "may":
		*v = 5
		return true
	case "jun", "june":
		*v = 6
		return true
	case "jul", "july":
		*v = 7
		return true
	case "aug", "august":
		*v = 8
		return true
	case "sep", "september":
		*v = 9
		return true
	case "oct", "october":
		*v = 10
		return true
	case "nov", "november":
		*v = 11
		return true
	case "dec", "december":
		*v = 12
		return true
	}
	return false
}

func (this *CronExp) getIntVal(s string, v *int) bool {
	if len(s) == 0 {
		return false
	}
	if s[0] >= '0' && s[0] <= '9' {
		v0 := StrToIntDef(s, -1)
		if v0 == -1 {
			return false
		}
		*v = v0
		return true
	}
	return false
}

func (this *CronExp) innerGetIntVal(s string, def int) (v int) {
	if !this.getIntVal(s, &v) {
		return def
	}
	return v
}

func (this *CronExp) innerWeekIntVal(s string, def int) (v int) {
	if !this.getIntVal(s, &v) {
		if !getWeekDayVal(s, &v) {
			return def
		}
	}
	return v
}

func (this *CronExp) innerMonthIntVal(s string, def int) (v int) {
	if !this.getIntVal(s, &v) {
		if !getMonthVal(s, &v) {
			return def
		}
	}
	return v
}

func (this *CronExp) calcuNextEx(exp string, c, min int, maxfn func(cnt int) (max int), valfn func(s string, def int) int) (span int) {
	strs := strings.Split(exp, ",")
	if len(strs) >= 2 { // 0,1,59
		f0 := valfn(strs[0], min)
		if f0 <= c {
			for i := 1; i < len(strs); i++ {
				v0 := valfn(strs[i], 0)
				if v0 > c {
					f0 = v0
				}
			}
		}

		if f0 > c {
			f1 := f0
			max := maxfn(0)
			if f1 >= max {
				f1 = max
			}
			if f1 > c {
				return f1 - c
			}
		}

		max := maxfn(0)

		// 0 - 59
		return (max - min + 1 + f0) - c
	}

	max := maxfn(0)
	v0 := valfn(exp, min)
	if v0 <= c {
		return (max - min + 1 + v0) - c
	} else {
		return v0 - c
	}
	return
}

func (this *CronExp) getNextForMonthDay(exp string, t0 time.Time) (new time.Time) {
	y, m0, d := t0.Date()
	h, mi, s := t0.Clock()
	m := int(m0)

	var encodefunc = func(y1, m1, d1 int) {
		new = time.Date(y1, time.Month(m1), d1, h, mi, s, 0, t0.Location())
	}

	var tryencodefn = func(y1, m1, d1 int) bool {
		if GetMonthDayN(y1, m1) >= d1 {
			encodefunc(y1, m1, d1)
			return true
		} else {
			return false
		}
	}

	strs := strings.Split(exp, ",")
	if len(strs) >= 2 { // 1,28,31
		f0 := this.innerGetIntVal(strs[0], 1)
		if f0 >= d {
			if tryencodefn(y, m, f0) {
				return
			}
		} else {
			// 取下一个测试
			for i := 1; i < len(strs); i++ {
				v0 := this.innerGetIntVal(strs[i], 1)
				if v0 >= d {
					if tryencodefn(y, m, v0) {
						return
					}
				}
			}
		}

		// 取第1个做next
		var y1, m1 = y, m
		for i := 0; i < 4; i++ {
			y1, m1 = NextMonthYM(y1, m1)
			if tryencodefn(y1, m1, f0) {
				return
			}
		}
		return t0 // 设定BUG
	}

	// 取第1个做next
	var y1, m1 = y, m
	v0 := this.innerGetIntVal(exp, 1)
	if v0 >= d {
		if tryencodefn(y1, m1, v0) {
			return
		}
	}

	for i := 0; i < 4; i++ {
		y1, m1 = NextMonthYM(y1, m1)
		if tryencodefn(y1, m1, v0) {
			return
		}
	}

	return t0 // 设定BUG
}

func (this *CronExp) calcuNext(exp string, c, min, max int, valfn func(s string, def int) int) (span int) {
	return this.calcuNextEx(exp, c, min, func(cnt int) int {
		return max
	}, valfn)
}

/*
*
注意事项：

	传入的时间如果是UTC则按照UTC进行计算

每一个域都使用数字，但还可以出现如下特殊字符，它们的含义是：

（1）*：表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。

（2）?：只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值，但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度，不管20日到底是星期几，则只能使用如下写法： 13 13 15 20 * ?, 其中最后一位只能用？，而不能使用*，如果使用*表示不管星期几都会触发，实际上并不是这样。

（3）-：表示范围。例如在Minutes域使用5-20，表示从5分到20分钟每分钟触发一次

（4）/：表示起始时间开始触发，然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次，而25，45等分别触发一次.

（5）,：表示列出枚举值。例如：在Minutes域使用5,20，则意味着在5和20分每分钟触发一次。

（6）L：表示最后，只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

（7）W:表示有效工作日(周一到周五),只能出现在DayofMonth域，系统将在离指定日期的最近的有效工作日触发事件。例如：在 DayofMonth使用5W，如果5日是星期六，则将在最近的工作日：星期五，即4日触发。如果5日是星期天，则在6日(周一)触发；如果5日在星期一到星期五中的一天，则就在5日触发。另外一点，W的最近寻找不会跨过月份 。

（8）LW:这两个字符可以连用，表示在某个月最后一个工作日，即最后一个星期五。

（9）#:用于确定每个月第几个星期几，只能出现在DayofMonth域。例如在4#2，表示某月的第二个星期三。
*/
func (this *CronExp) GetNextTime(now time.Time) (new time.Time) {
	if now.IsZero() {
		now = time.Now().Add(time.Second).Truncate(time.Second)
	} else {
		ns := now.Nanosecond()
		if ns > 0 {
			now = now.Add(time.Second).Truncate(time.Second)
		}
	}
	new = now
	new = this.seconds.NextValFunc(new, new.Second())
	new = this.minutes.NextValFunc(new, new.Minute())
	new = this.hours.NextValFunc(new, new.Hour())

	if len(this.dayofmonth) > 0 {
		new = this.getNextForMonthDay(this.dayofmonth, new)
	}

	// , - * /    四个字符
	if len(this.year) > 0 {

	}

	// , - * ? / L C #     八个字符   1~7的整数或者 SUN-SAT （1=SUN）
	if len(this.dayofweek) > 0 {

	}

	// 去掉毫秒数, 避免到0.999对比s时出现忽略掉的情况
	return new
}

func (this *CronExp) ParseText(s string) error {
	return this.ParseTextEx(s, time.Now().Add(time.Second))
}

func (this *CronExp) ParseTextEx(s string, now time.Time) error {
	this.text = s
	strs := strings.Split(s, " ")
	{ // 处理 *
		var i = len(strs) - 1
		for ; i > 0; i-- {
			if strs[i] == "*" || strs[i] == "" || strs[i] == "?" {

			} else {
				break
			}
		}
		strs = strs[:i+1]
	}

	j := 0
	this.seconds.Reset()
	this.minutes.Reset()
	this.hours.Reset()
	this.dayofmonth = ""
	this.dayofweek = ""
	this.month = ""
	this.year = ""
	for i := 0; i < len(strs); i++ {
		if len(strs[i]) > 0 {
			switch j {
			case 0:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Second()))
				e1 := ParseExpItem(&this.seconds, str, 0, 59, time.Second)
				if e1 != nil {
					return e1
				}
				j++
			case 1:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Minute()))
				e1 := ParseExpItem(&this.minutes, str, 0, 59, time.Minute)
				if e1 != nil {
					return e1
				}
				j++
			case 2:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Hour()))
				e1 := ParseExpItem(&this.hours, str, 0, 23, time.Hour)
				if e1 != nil {
					return e1
				}
				j++
			case 3:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Day()))
				this.dayofmonth = str
				j++
			case 4:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Month()))
				this.month = str
				j++
			case 5:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Weekday()))
				this.dayofweek = str
				j++
			case 6:
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Year()))
				this.year = str
				j++
			default:
				break
			}
		}
	}

	return nil
}

/*
*

	Seconds Minutes Hours DayofMonth Month DayofWeek Year
	* 0/1  /home/xxx
*/
func SplitCronExp(s string) (exp string, remain string) {

	now := time.Now()

	strs := strings.SplitN(s, " ", 8)
	if len(strs) == 0 {
		return
	}

	j := 0
	var seconds, minutes, hours, days CronExpItem
	for i := 0; i < len(strs); i++ {
		if len(strs[i]) > 0 {
			switch j {
			case 0:
				str := strs[i]
				if str == "*" {
					j++
					continue
				}
				e1 := ParseExpItem(&seconds, str, 0, 59, time.Second)
				if e1 != nil {
					break
				}
				j++
			case 1:
				str := strs[i]
				if str == "*" {
					j++
					continue
				}
				e1 := ParseExpItem(&minutes, str, 0, 59, time.Minute)
				if e1 != nil {
					break
				}
				j++
			case 2:
				str := strs[i]
				if str == "*" {
					j++
					continue
				}
				e1 := ParseExpItem(&hours, str, 0, 23, time.Hour)
				if e1 != nil {
					break
				}
				j++
			case 3: // day of month
				str := strs[i]
				str = strings.ReplaceAll(str, "*", fmt.Sprintf("%d", now.Day()))

				e1 := ParseExpItem(&days, str, 0, 23, time.Hour*24)
				if e1 != nil {
					break
				}
				j++
			case 4:
				// month
				str := strs[i]
				if str == "*" {
					j++
					continue
				}
				var v int

				if getMonthVal(str, &v) {
					j++
					continue
				}

				// 只是测试是否合法数据
				e1 := ParseExpItem(&days, str, 1, 12, time.Hour)
				if e1 != nil {
					break
				}

				j++
			case 5:
				str := strs[i]
				if str == "*" {
					j++
					continue
				}

				var v int
				if getWeekDayVal(str, &v) {
					j++
					continue
				}

				// 只是测试是否合法数据
				e1 := ParseExpItem(&days, str, 1, 7, time.Hour*24)
				if e1 != nil {
					break
				}
				j++
			case 6:
				str := strs[i]
				if str == "*" {
					j++
					continue
				}
				// 只是测试是否合法数据
				e1 := ParseExpItem(&days, str, 2020, 99999, time.Hour*24)
				if e1 != nil {
					break
				}
				j++
			default:
				break
			}
		}
	}
	exp = strings.Join(strs[:j], " ")
	remain = strings.Join(strs[j:], " ")
	return
}
